//
// Created by luo on 2022-09-11.
//

#include "../opencv/OpenCv.h"

/*
 * 图像形态学用具有一定形态的结构元素度量和提取图像中的对应形状 以达到对图像分析和识别的目的
 * 主要包括图像腐蚀、膨胀、 开运算与闭运算
 * 从图像中提取对于表达和描述区域形状有意义的图像分嚣
 * 以便后续的识别工作能够抓住对象最为本质的形状特性，例如边界、连通域等.
 * 重点关注物体区域信息，常将图像转化为二值图后再进行图像形态学分析
 * */

/*
 * 图像像素距离变换，常用的距离有欧氏距离、街区距离和棋盘距离
 * 欧氏距离：指两个像素点之间的直线距离
 * 街区距离：指两个像素点X方向和Y方向的距离之和（障碍物的限制导致不能走直线距离）
 * 棋盘距离：指两个像素点X方向距离和Y方向距离的最大值（两个像素点移动到同一行或同一列时最大距离）
 * */
void QuickDemo::my_imagedistance_change(Mat &mat)//图像像素距离变换
{
    /*
     * 计算图像中不同像素之间距离,实现图像距离变换，统计图像中所有像素距离0像素的最小距离
     * void distanceTransform(src,dst,labels,distanceType,maskSize,labelType)
     * 输入图，输出图，二维标签数组(CV_32S单通道数据)，计算两个像素距离的方法标志，距离变换掩码矩阵尺寸,要构建的标签数组类型
     * DIST_USER 自定义距离 1 2 3 分别对应街区/欧氏/棋盘距离
     * DIST_MASK_3(3*3) DIST_MASK_5(5*5)
     * */

}

/*
 * 图像的连通域是指图像中具有相同像素值并且位置相邻的像素组成的区域
 * 连通域分析：在图像中寻找彼此互相独立的连通域并将其标记出来
 * 可应用于车牌识别、文字识别、目标检测等，对感兴趣区域分割与识别
 * 一般而言，一个连通域内只包含一个像素值，分析时使用二值化处理后图像
 * 像素相邻  4邻域：两个像素相邻必须在水平和垂直方向上相邻，相邻的两个
            像素坐标必须只有一位不同而且只能相差1个像素。
            8邻域：在这种定义下，两个像素相邻允许在对角线方向相邻，
            相邻的两个像素坐标在X方向和Y方向上的最大差值为1.
 * 邻域分析法：两遍扫描法、种子填充法
 * */

int dst_value = 73;
cv::Mat rice,riceBW;
cv::Mat out_1,out_2,stats,centroids;
static void onChange(int,void*)
{
    threshold(rice,riceBW,dst_value,255,THRESH_BINARY);
    //cv::namedWindow("dst",WINDOW_NORMAL);
    cv::imshow("dst",riceBW);
}

void QuickDemo::my_imageConnecteddomain_analyse(Mat &mat)//图像连通域分析
{
    /* 提取图像中不同连通域，计算二值图像中连通域个数,在图像中将不同的联通域用不同的数字标签标
        其中标签0表示图像中的背景区域
     * 第一种原型
     * void connectedComponents(image,labels,connectivity,ltype,ccltype);
     * 待标记单通道图(CV_8U),标记连通域后输出图，邻域种类
     * 输出图像的数据类型(CV_16U/CV_32S),标记连通域时使用的算法类型标志
     * CCL_WU CCL_DEFAULT CCL_GRANA
     *
     * 第二种原型(带默认值的简易型)
     * void connectedComponents(image,labels,connectivity,ltype)
     * 后两参数默认为8，CV_32S
     * */

    /*
    * 在图像中不同连通域标记标签的同时统计每个连通域的中心位置、矩形区域大小、区域面积等信息
    * int connectedComponentsWithStats(image,labels,stats,centroids,connectivity,ltype,ccltype);
    * stats 含有不同连通域统计信息的矩阵，矩阵的数据类型为CV_32S。
    * centroids 每个连通域质心的坐标位置CV_64F。
    * */

    //将图像转换为二值图，统计连通域
    cv::cvtColor(mat,rice,COLOR_BGR2GRAY);
    //cv::namedWindow("mark",WINDOW_NORMAL);
    //cv::imshow("mark",rice);
    threshold(rice,riceBW,dst_value,255,THRESH_BINARY);
    //cv::createTrackbar("liantong","mark",&dst_value,255,onChange,0);
    //生成随机颜色，用于区分不同连通域;
    RNG rng(10086);

    //int number = cv::connectedComponents(riceBW,out_1,8,CV_16U);//统计图像中连通域的个数
    int number = cv::connectedComponentsWithStats(riceBW,out_2,stats,centroids,8,CV_16U);//统计图像中连通域的个数
    std::vector<Vec3b> colors;
    for(int i = 0; i < number; i++)
    {
        //均匀分布的随机数确定颜色
        Vec3b vec3 = Vec3b(rng.uniform(0,256),rng.uniform(0,256),rng.uniform(0,256));
        colors.push_back(vec3);
    }
    //用不同颜色标记不同连通域
    cv::Mat result = cv::Mat::zeros(rice.size(),mat.type());
    int w = result.cols;
    int h = result.rows;
//    for(size_t row = 0; row < h; row++)
//    {
//        for(size_t col = 0; col <w; col++)
//        {
//            int label = out_1.at<uint16_t>(row,col);//得到out图像的每行每列像素值，存放在label变量内
//            if(label == 0)//背景为黑色则不改变
//            {
//                continue;
//            }
//            result.at<Vec3b>(row,col) = colors[label];//相同像素的话，颜色标签相同，对应的颜色也相同
//        }
//    }
    for(int i = 1; i< number;i++)
    {
        //中心位置
        int center_x = centroids.at<double>(i,0);
        int center_y = centroids.at<double>(i,1);
        int area = stats.at<int>(i,CC_STAT_AREA);
        //中心位置绘制
        cv::circle(mat,Point(center_x,center_y),2,Scalar(0,255,0),2,8,0);
        //矩形边框+外接矩形
        Rect rect(stats.at<int>(i,CC_STAT_LEFT),stats.at<int>(i,CC_STAT_TOP),
                stats.at<int>(i,CC_STAT_WIDTH),stats.at<int>(i,CC_STAT_HEIGHT));
        cv::rectangle(mat,rect,colors[i],1,8,0);
        cv::putText(mat,format("%d",i),Point(center_x,center_y),FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,255),1);
        std::cout<<"number: "<<i<<" area: "<<area<<std::endl;
    }
    //cv::imshow("result",result);
    cv::imshow("mat",mat);
    cv::waitKey(0);
    cv::destroyAllWindows();

}

/*
 * 图像腐蚀，类似图像卷积，都需要模板矩阵来控制运算的结果。
 * 在图像的腐蚀和膨胀中这个模板矩阵称为结构元素。
 * 结构元素遍历原图，所到处所有像素值均不为0，保留结构元素中心点对应的图像像素，否则删除结构元素的中心点对应像素
 * 将结构元素中心点依次与原图中的每个像素重合 判断每-个像萦点是保留还是删除。
 * */

//绘制包含连通域区域的函数
void drawState(cv::Mat &img,int number,cv::Mat centroids,cv::Mat stats,String str)
{
    RNG rng(10086);
    std::vector<Vec3b> colors;
    for(int i = 0; i < number; i++)
    {
        //均匀分布的随机数确定颜色
        Vec3b vec3 = Vec3b(rng.uniform(0,256),rng.uniform(0,256),rng.uniform(0,256));
        colors.push_back(vec3);
    }
    for(int i = 1; i< number;i++)
    {
        //中心位置
        int center_x = centroids.at<double>(i,0);
        int center_y = centroids.at<double>(i,1);
        //中心位置绘制
        cv::circle(img,Point(center_x,center_y),2,Scalar(0,255,0),2,8,0);
        //矩形边框+外接矩形
        Rect rect(stats.at<int>(i,CC_STAT_LEFT),stats.at<int>(i,CC_STAT_TOP),
                  stats.at<int>(i,CC_STAT_WIDTH),stats.at<int>(i,CC_STAT_HEIGHT));
        cv::rectangle(img,rect,colors[i],1,8,0);
        cv::putText(img,format("%d",i),Point(center_x,center_y),
                    FONT_HERSHEY_SIMPLEX,0.5,Scalar(0,0,255),1);
    }
    cv::imshow(str,img);
}

/*
 * 腐蚀和膨胀是形态学的基本运算，通过这些基本运算可以去除图像中的噪声、
 * 分割出独立的区域或者将两个连通域连接在一起等
 * 形态学操作其实就是改变物体的形状，比如腐蚀就是”变瘦”，膨胀就是”变胖”
 * 腐蚀和膨胀针对图像中的物体部分,腐蚀使得高亮区域或者白色区域变小，膨胀则反之
 * */
void QuickDemo::my_image_erode(Mat &mat)
{
    /*
     * 生成常用的矩形结构元素/十字结构元素/椭圆结构元素
     * void getStructuringElement(shape,ksize,anchor);
     * 生成结构元素种类，结构元素尺寸(尺寸越大，腐蚀效果越明显)，中心点位置
     * MORPH_RECT 矩形结构元素(all 1) MORPH_ELLIPSE 椭圆(矩形内接椭圆元素为1)
     * 只有十字结构元素的中心点位置会影响图像腐蚀后的轮廓形状，其他种类结构元素的中心点位置只影响形态学操作结果的平移量。
     * */

    /*
     * 图像腐蚀
     * void erode(src,dst,kernel,anchor,iterations,boderType,borderValue);
     * kernel 自定义或getStructuringElement生成  iterations 腐蚀次数(越多越明显，默认为1)
     * 腐蚀过程中只针对图像非0像素,可以去除由噪声引起的较小连通域
     * */
    //生成用于腐蚀的原图
    cv::Mat src = (cv::Mat_<uchar>(6,6)<<0,0,0,0,255,0,
            0,255,255,255,255,255,
            0,255,255,255,255,0,
            0,255,255,255,255,0,
            0,255,255,255,255,0,
            0,0,0,0,0,0);
    cv::Mat struct_rect,struct_cross;
    struct_rect = getStructuringElement(MORPH_RECT,Size(3,3));//矩形结构元素 0
    struct_cross = getStructuringElement(MORPH_CROSS,Size(3,3));//十字结构元素 1

    cv::Mat erodeSrc; //存放腐蚀后的图
    cv::erode(src,erodeSrc,struct_cross);
    cv::namedWindow("src",WINDOW_NORMAL);
    cv::namedWindow("erodeSrc",WINDOW_NORMAL);
    cv::imshow("src",src);
    cv::imshow("erodeSrc",erodeSrc);

    cv::Mat LearnCV_black = imread("C:\\Pictures\\LearnCV_black.jpg",IMREAD_ANYDEPTH);
    cv::Mat LearnCV_write = imread("C:\\Pictures\\LearnCV_write.jpg",IMREAD_ANYDEPTH);//原图形式读入
    cv::Mat erode_black1,erode_black2,erode_write1,erode_write2,mask;
    if (LearnCV_black.empty() || LearnCV_write.empty()) {
        cout << "Error" << endl;
        return ;
    }
    //cv::bitwise_not(LearnCV_write,LearnCV_write,mask);
    //cv::imwrite("C:\\Pictures\\LearnCV_black.jpg",LearnCV_write);

    //黑色背景图腐蚀
    cv::erode(LearnCV_black,erode_black1,struct_rect);//矩形
    cv::erode(LearnCV_black,erode_black2,struct_cross);//十字形
    cv::namedWindow("LearnCV_black",WINDOW_AUTOSIZE);
    cv::imshow("LearnCV_black",LearnCV_black);
    cv::imshow("erode_black1",erode_black1);
    cv::imshow("erode_black2",erode_black2);

    //白色背景图腐蚀
    cv::erode(LearnCV_write,erode_write1,struct_rect);//矩形
    cv::erode(LearnCV_write,erode_write2,struct_cross);//十字形
    cv::namedWindow("LearnCV_write",WINDOW_AUTOSIZE);
    cv::imshow("LearnCV_write",LearnCV_write);
    cv::imshow("erode_write1",erode_write1);
    cv::imshow("erode_write2",erode_write2);

    //验证腐蚀对小连通域去除
    cv::Mat mat2,humen,humenBW;
    copyTo(mat,mat2,mat);
    cv::cvtColor(mat,humen,COLOR_BGR2GRAY);
    cv::threshold(humen,humenBW,73,255,THRESH_BINARY);
    cv::Mat out,stats,centroids;
    int number = connectedComponentsWithStats(humenBW,out,stats,centroids,8,CV_16U);
    drawState(mat,number,centroids,stats,"before erode area");

    cv::erode(humenBW,humenBW,struct_rect);
    int numbers = connectedComponentsWithStats(humenBW,out,stats,centroids,8,CV_16U);
    drawState(mat2,number,centroids,stats,"after erode area");
    cv::waitKey(0);

}

/*
 * 图像膨胀与图像腐蚀相反，结构元素所覆盖的范围，像素值若与结构元素中心值不同
 * 则该点像素值被覆盖为中心点对应像素值，相同则保留。
 * 将结构元素中心点依次与原图中的每个像素重合，判断是否有需要填充的像素
 * */
void QuickDemo::my_image_dilate(Mat &mat)
{
    /*
     * void dilate(src,dst,kernel,anchor,iterations,borderType,Scalar);
     * iterations为膨胀次数
     * 膨胀过程只针对图像中的非零像索
     * */

    cv::Mat src = (cv::Mat_<uchar>(6,6)<<0,0,0,0,255,0,
            0,255,255,255,255,255,
            0,255,255,255,255,0,
            0,255,255,255,255,0,
            0,255,255,255,255,0,
            0,0,0,0,0,0);
    cv::Mat struct_rect,struct_cross;
    struct_rect = getStructuringElement(MORPH_RECT,Size(3,3));//矩形结构元素 0
    struct_cross = getStructuringElement(MORPH_CROSS,Size(3,3));//十字结构元素 1

    cv::Mat dilateSrc; //存放膨胀后的图
    cv::dilate(src,dilateSrc,struct_cross);
    cv::namedWindow("src",WINDOW_NORMAL);
    cv::namedWindow("erodeSrc",WINDOW_NORMAL);
    cv::imshow("src",src);
    cv::imshow("erodeSrc",dilateSrc);

    cv::Mat LearnCV_black = imread("C:\\Pictures\\LearnCV_black.jpg",IMREAD_ANYDEPTH);
    cv::Mat LearnCV_write = imread("C:\\Pictures\\LearnCV_write.jpg",IMREAD_ANYDEPTH);//原图形式读入
    cv::Mat dilate_black1,dilate_black2,dilate_write1,dilate_write2,mask;
    if (LearnCV_black.empty() || LearnCV_write.empty()) {
        cout << "Error" << endl;
        return ;
    }

    //黑色背景图腐蚀
    cv::erode(LearnCV_black,dilate_black1,struct_rect);//矩形
    cv::erode(LearnCV_black,dilate_black2,struct_cross);//十字形
    cv::namedWindow("LearnCV_black",WINDOW_AUTOSIZE);
    cv::imshow("LearnCV_black",LearnCV_black);
    cv::imshow("dilate_black1",dilate_black1);
    cv::imshow("dilate_black2",dilate_black2);

    //白色背景图腐蚀
    cv::erode(LearnCV_write,dilate_write1,struct_rect);//矩形
    cv::erode(LearnCV_write,dilate_write2,struct_cross);//十字形
    cv::namedWindow("LearnCV_write",WINDOW_AUTOSIZE);
    cv::imshow("LearnCV_write",LearnCV_write);
    cv::imshow("dilate_write1",dilate_write1);
    cv::imshow("dilate_write2",dilate_write2);

    //验证膨胀与腐蚀的结果关系
    /*
     * 求取黑色背景图像的腐蚀结果与自色背景图像的膨胀结果进行逻辑"与"逻辑"异或"运算
     * */
    cv::Mat erode_black1,resultXor,resultAnd;
    cv::erode(LearnCV_black,erode_black1,struct_rect);
    cv::bitwise_xor(erode_black1,dilate_write1,resultXor);
    cv::bitwise_and(erode_black1,dilate_write1,resultAnd);
    cv::imshow("resultXor",resultXor);
    cv::imshow("resultAnd",resultAnd);
    cv::waitKey(0);
}

/*
 * 图像形态学腐蚀可以将细小的噪声区域去除 但是会将图像主要区域的面积缩小，造成主要区域的形状发生改变.
 * 图像形态学膨胀可以扩充每一个区域的面积，填充较小的空洞 但是会增加噪声的面积
 * 将腐蚀与膨胀适当结合，既可去除噪声，又不缩小图像中主要区域面积，既填充较小空洞，又不增加噪声面积
 * */

/*
 * 图像开运算可以去除图像中的噪声,消除较小连通域,保留较大连通域，同时能够在两个物体
    纤细的连接处将两个物体分离,并且在明显改变较大连通域面积的同时能够平滑连通域的边界.
 * 首先对图像进行腐蚀 消除图像中的噪声和较小的连通域，之后通过膨胀运算弥补较大连通域因腐蚀而造成的面积减小
 * 简言之，图像开运算就是只去除了较小的连通域 保留了较大的连通域。
 * */

/*
 * 图像闭运算可以去除连通域内的小型空洞 平滑物体轮廓 连接两个临近的连通域。
 * 闭运算首先对图像进行膨胀，填充连通域内的小型空洞，扩大连通域的边界
    将临近的两个连通域连接，之后通过腐蚀运算减少由膨胀运算引起的连通域边界的扩大以及面积的增加
 * */

/*
 * 形态学梯度，能够描述目标的边界，根据图像腐蚀和膨胀与原图之间的关系计算得到
 * 分为基本梯度、内部梯度和外部梯度：
 * 基本梯度是原图像膨胀后图图像与腐蚀后图像间的差。
   内部梯度图像是原图像与腐蚀后图像间的差值图。
   外部梯度是膨胀后图像与原图像间的差值图像。
 * */

/*
 * 顶帽运算是原图像与开运算结果之间的差值，往往用来分离比邻近点亮一些的斑块,放大了裂缝或者局部低亮度的区域.
 * 黑帽运算是原图像与顶帽运算结果之间的差值,往往用来分离比邻近点暗一些的斑块。
 * */

/*
 * 击中击不中变换与腐蚀类似，但后者要求原图像中需要存在与结构元素一模一样的结构，
 * 即结构元素中非零元素也需要同时被考虑
 * */

void QuickDemo::my_morphologyEx(Mat &mat) //形态学操作
{
    /*
     * void morphologyEx(src,dst,op,kernel,anchor,iterations,borderType,Scalar);
     * op为形态学操作类型标志
     * MORPH_ERODE MORPH_DILATE MORPH_OPEN MORPH_CLOSE
     * MORPH_GRADIENT(梯度) MORPH_TOPHAT(顶帽) MORPH_BLACKHAT(黑帽)
     * */

    /*
     * 开运算，先腐蚀后膨胀，去除较小连通域，保留较大连通域，减小连通域面积
     * 闭运算，先膨胀后腐蚀，去除连通域内小型空洞，连接两个临近连通域，扩大连通域面积
     * 形态学梯度，利用腐蚀、膨胀和原图的计算得出，能够描述目标的边界
     * 顶帽运算，原图-开运算结果图，分离比邻近点亮一些的斑块,放大裂缝或者局部低亮度区域
     * 黑帽运算，闭运算结果图-原图，分离比邻近点暗一些的斑块
     * 击中与击不中变换，与腐蚀类似，但还要要求考虑结构元素中的非零元素
     * */
    cv::Mat src = (cv::Mat_<uchar>(9,12)<<0,0,0,0,0,0,0,0,0,0,0,0,
            0,255,255,255,255,255,255,255,0,0,255,0,
            0,255,255,255,255,255,255,255,0,0,0,0,
            0,255,255,255,255,255,255,255,0,0,0,0,
            0,255,255,255,0,255,255,255,0,0,0,0,
            0,255,255,255,255,255,255,255,0,0,0,0,
            0,255,255,255,255,255,255,255,0,0,255,0,
            0,255,255,255,255,255,255,255,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0
            );
    cv::namedWindow("src",WINDOW_NORMAL);
    cv::imshow("src",src);

    cv::Mat kernel = cv::getStructuringElement(MORPH_RECT,Size(3,3));
    //对二值化矩阵进行形态学操作
    cv::Mat open,close,gradient,tophat,blackhat,hitmiss;
    morphologyEx(src,open,MORPH_OPEN,kernel);
    cv::namedWindow("open",WINDOW_NORMAL);
    cv::imshow("open",open);

    morphologyEx(src,close,MORPH_CLOSE,kernel);
    cv::namedWindow("close",WINDOW_NORMAL);
    cv::imshow("close",close);

    morphologyEx(src,gradient,MORPH_GRADIENT,kernel);
    cv::namedWindow("gradient",WINDOW_NORMAL);
    cv::imshow("gradient",gradient);

    morphologyEx(src,tophat,MORPH_TOPHAT,kernel);
    cv::namedWindow("tophat",WINDOW_NORMAL);
    cv::imshow("tophat",tophat);

    morphologyEx(src,blackhat,MORPH_BLACKHAT,kernel);
    cv::namedWindow("blackhat",WINDOW_NORMAL);
    cv::imshow("blackhat",blackhat);

    morphologyEx(src,hitmiss,MORPH_HITMISS,kernel);
    cv::namedWindow("hitmiss",WINDOW_NORMAL);
    cv::imshow("hitmiss",hitmiss);

    //用图像验证形态学操作效果
    cv::cvtColor(mat,mat,COLOR_BGR2GRAY);
    cv::threshold(mat,mat,73,255,THRESH_BINARY);
    cv::imshow("after erzhihua img",mat);
    cv::Mat kernel_mat = cv::getStructuringElement(MORPH_RECT,Size(5,5),Point(-1,-1));
    cv::Mat open_mat,close_mat,gradient_mat,tophat_mat,blackhat_mat,hitmiss_mat;
    morphologyEx(mat,open_mat,MORPH_OPEN,kernel_mat);
    morphologyEx(mat,close_mat,MORPH_CLOSE,kernel_mat);
    morphologyEx(mat,gradient_mat,MORPH_GRADIENT,kernel_mat);//基本梯度
    morphologyEx(mat,tophat_mat,MORPH_TOPHAT,kernel_mat);
    morphologyEx(mat,blackhat_mat,MORPH_BLACKHAT,kernel_mat);
    morphologyEx(mat,hitmiss,MORPH_HITMISS,kernel_mat);
    cv::imshow("open_mat",open_mat);
    cv::imshow("close_mat",close_mat);
    cv::imshow("gradient_mat",gradient_mat);
    cv::imshow("tophat_mat",tophat_mat);
    cv::imshow("blackhat_mat",blackhat_mat);
    cv::imshow("hitmiss",hitmiss);

    cv::waitKey(0);

}

void QuickDemo::my_image_refine(Mat &mat)//图像细化
{

}